#include "Bootstrap.h"

#include "model\Asteroid.h"
#include "model\Bonus.h"
#include "model\Bullet.h"
#include "model\Scene.h"
#include "model\Ship.h"
#include "model\EntityFactory.h"
#include "view\Window.h"
#include "view\StartWindow.h"
#include "sdl\SDLSurface.h"
#include "sdl\mixer\mixer.h"
#include "io\Dir.h"

using namespace net;
using namespace concurrent;
using namespace model;
using namespace view;
using namespace sdl;

Bootstrap::Bootstrap(void)
{
	showGameView = false;
	selectedShip = "nave1";//default
	AsteroidsMenuListener* listener = new AsteroidsMenuListener(this);	
	window = new StartWindow(listener);
	mainSurface = window->getSurface();
	sound = new Mixer();
	otherPlayerName = "";
}

Bootstrap::~Bootstrap(void)
{
	delete window;
}

void Bootstrap::start()
{	
	SceneRepository* sceneRepository = new SceneRepository(CONFIG_SERVER_DIRECTORY);
	window->addScenes(sceneRepository->getSceneNames());
	delete sceneRepository;
	showMenu();
}

void Bootstrap::showMenu()
{
	window->show();
	if (showGameView)
	{
		showGame();
		gameFinished();
	}
}

void Bootstrap::launchServer()
{	
	string portStr = window->getServerPort();
	int port = atoi(portStr.c_str());
	string sceneName = window->getSceneName();
	sceneName.append(".xml");
	cout << "selected scenario is: " << sceneName << endl;

	server.reset(new AsteroidsServer(this, port, 2, "..\\config\\server", sceneName));
	Thread t(server.get());
	t.start();
}

void Bootstrap::launchClient()
{
	string ip = window->getClientHost();
	string portStr = window->getClientPort();
	selectedShip = window->getSelectedShipName();
	string playerName = window->getPlayerName();
	int port = atoi(portStr.c_str());
	client.reset(new AsteroidsClient(this, ip, port, selectedShip, playerName));
	Runnable* c = (Runnable*) client.get();
	Thread tc(c);
	tc.start();
}

void Bootstrap::startGame(string sceneFile)
{
	window->close();
	gameSceneFile = sceneFile;
	showGameView = true;
	gameInitialized = false;
}

void Bootstrap::showGame()
{
	try
	{
		cout << "Loading received scene..." << endl;
		EntityRepository repository;
		Scene* scene = repository.loadScene(gameSceneFile);

		cout << "Creating client view..." << endl;
		GameClient* c = (GameClient*) client.get();
		clientView.reset(new ClientView(mainSurface, scene->getWidth(), scene->getHeight(), c, scene->getBackground())); 

		list<Asteroid*> asteroidsList = scene->getAsteroids();
		list<Asteroid*>::iterator itAsteroids;
		for (itAsteroids=asteroidsList.begin(); itAsteroids!=asteroidsList.end(); itAsteroids++)
		{
			Asteroid * a = (*itAsteroids);
			asteroidsmap.insert(pair<int,Asteroid*>(a->getId(), a));
			clientView->addEntity(a);
		}

		ships = scene->getShips();
		list<Ship*>::iterator itShips;
		for (itShips=ships.begin(); itShips!=ships.end(); itShips++)
		{
			if ((*itShips)->getId() == this->ownShipId)
			{
				(*itShips)->setIsOwnEntity(true);
				clientView->initializeIndicators((*itShips));

				if ((*itShips)->getBase() && !window->getPlayerName().empty())
				{
					(*itShips)->getBase()->setName(window->getPlayerName());
				}
			}
			else
			{
				if ((*itShips)->getBase() && !this->otherPlayerName.empty())
				{
					(*itShips)->getBase()->setName(this->otherPlayerName);
				}
			}
			clientView->addEntity((*itShips));
		}

		bases = scene->getBases();
		list<Base*>::iterator itBases;
		for (itBases=bases.begin(); itBases!=bases.end(); itBases++)
		{
			clientView->addEntity((*itBases));
		}
		scene->clear();	
		cout << "NaveID: " << this->ownShipId << endl;

		gameInitialized = true;
		//sound->playMusic("..\\sounds\\musica_de_fondo.mid");
		clientView->show();
	}
	catch (...)
	{
		gameFinished();
	}
}

void Bootstrap::cleanPendingEntities()
{
	// limpia las entidades que debian ser borradas
	if (pendingToDelete.size() > 0)
	{
		for_each(pendingToDelete.begin(), pendingToDelete.end(), Utils::delete_object());
		pendingToDelete.clear();
	}
}

void Bootstrap::updateEntities(map<int, EntityData> asteroidsData, list<ShipData> shipsData, map<int, BulletData> bulletsData, map<int, EntityData> bonusData)
{
	try
	{
		if (clientView.get() && gameInitialized)
		{						
			cleanPendingEntities();
		
			list<Ship*>::iterator itS;
			list<EntityData>::iterator it;

		    // Actualizacion de asteroides
			// Si un asteroide de la lista del cliente no esta en la del servidor, entonces desaparecio y se tiene q borrar
		    map<int, Asteroid*>::iterator itClientA;
			if (asteroidsData.size() != asteroidsmap.size())
			{
				//sound->freeChannelOne();
				//sound->playEffect("CHOQUE");
			}
			for (itClientA=asteroidsmap.begin(); itClientA!=asteroidsmap.end();)
			{		
				if (asteroidsData.find(itClientA->first) == asteroidsData.end())
				{
					clientView->removeEntity(itClientA->second);
					pendingToDelete.push_back(itClientA->second);//lo agruego a la lista de pendientes para borrar que se limpia en la proxima actualizacion					
					itClientA = asteroidsmap.erase(itClientA);	
				}
				else
				{
					// Actualizacion de la posicion
					EntityData &a = asteroidsData[itClientA->first];
					itClientA->second->setPosition(a.x, a.y);					
					itClientA++;
				}		
			}

			//Si un asteroide de la lista del servidor no esta en la del cliente, entonces es nuevo y se agrega
			map<int, EntityData>::iterator itServer1;
			for (itServer1=asteroidsData.begin(); itServer1!=asteroidsData.end(); itServer1++)
			{
				//cout << "Id: " <<  (itServer1)->second.id << " (x,y) (" <<  (itServer1)->second.x << "," <<  (itServer1)->second.y << ") " << endl;
				if (asteroidsmap.find(itServer1->first) == asteroidsmap.end())
				{
					EntityData &a = itServer1->second;
					//busca que exista el tipo que recibio
					if (a.type != 0)
					{
						string name = "";
						string path = "";
						if (a.type == ASTEROID_CIRCULO)
						{
							name = ASTEROID_CIRCULO_NAME;
							path = ASTEROID_CIRCULO_PATH;
						}
						else if (a.type == ASTEROID_RECTANGULO)
						{
							name = ASTEROID_RECTANGULO_NAME;
							path = ASTEROID_RECTANGULO_PATH;
						}
						else if (a.type == ASTEROID_L)
						{
							name = ASTEROID_L_NAME;
							path = ASTEROID_L_PATH;
						}
						else if (a.type  == ASTEROID_CUADRADO)
						{
							name = ASTEROID_CUADRADO_NAME;
							path = ASTEROID_CUADRADO_PATH;
						}

						AsteroidType* asteroidType = new AsteroidType(name);
						asteroidType->setHeight(a.height);
						asteroidType->setWidth(a.width);
						asteroidType->setImagePath(path);
						asteroidType->setRotationAngle((int)(a.rotation+0.5));
						
						Asteroid* asteroid = EntityFactory::instance().createAsteroid(a.id, asteroidType, Point(a.x, a.y), a.direction, a.rotation, a.velocity);
						asteroid->setImagePath(path);										

						asteroidsmap.insert(pair<int, Asteroid*>(a.id, asteroid));
						clientView->addEntity(asteroid);						
					}
				}
			}			

			// actualiza naves
			list<ShipData>::iterator itsd;
			for (itsd=shipsData.begin(); itsd!=shipsData.end(); itsd++)
			{
				for (itS=ships.begin(); itS!=ships.end(); itS++)		
				{
					ShipData& sd = (*itsd);
					if (sd.id == (*itS)->getId())
					{
						(*itS)->setRotation(sd.rotation);
						(*itS)->setPosition(sd.x, sd.y);						
						if (sd.acelerated)
						{
							(*itS)->acelerate();
						}
						else
						{
							(*itS)->stopAceleration();					
						}
						
						if (sd.isSuperShield)
						{
							(*itS)->getSuperShield()->enable();
						}
						else
						{
							(*itS)->getSuperShield()->desable();
							//TODO Falta meter la logica para que deshabilite el super escudo.
						}

						if (sd.isInvisibilityEnabled)
						{
							(*itS)->enableInvisibility();
						}
						else			
						{
							(*itS)->disableInvisibility();
						}

						if (sd.isDeadEnabled)
						{
							(*itS)->enableIsDead();
						}
						else
						{
							(*itS)->disableIsDead();							
						}
						
						if (sd.isShield)
						{
							(*itS)->getShield()->enable();
						}
						else
						{
							(*itS)->getShield()->desable();
						}
						
						if (sd.isBombEnabled)
						{
							(*itS)->enableBomb();
						}
						else
						{
							(*itS)->disableBomb();
						}

						if (sd.isLaserActivated)
						{
							(*itS)->enableLaser();
						}
						else
						{
							(*itS)->desableLaser();
						}

						if (sd.numberOfLifes != (*itS)->getNumberOfLifes())
						{
							//sound->freeChannelOne();
							//sound->playEffect("CHOQUE");
						}

						(*itS)->setNumberOfLifes(sd.numberOfLifes);
						(*itS)->setLife(sd.shipLife);
						(*itS)->setMissiles(sd.numberMisiles);
						(*itS)->setShieldEnergy(sd.shieldLife);
						(*itS)->getBase()->setLife(sd.baseLife);
						
						if ((*itS)->getNumberOfLifes() == 0 || (*itS)->getBase()->getLife() == 0 )
						{
							client->sendEvent(GameEvent::QUIT);
							quitGame();
						}

						//if ((*itS)->getIsDead())
							//sound->playEffect("CHOQUE");
						break;
					}
				}
			}

			//for (itS=ships.begin(); itS!=ships.end(); itS++)		
			//{
			//	if ((*itS)->isOwnEntity())
			//	{
			//		cout << "------------ SHIP " << (*itS)->getId() << " -------------------- " << endl;
			//		cout << "Number of Lifes" << (*itS)->getNumberOfLifes() << endl;
			//		cout << "Life (energia) " << (*itS)->getLife() << endl;			
			//		cout << "Misiles " << (*itS)->getMissilesChargeCount() << endl;
			//		cout << "Super Shield activo? " << (*itS)->getSuperShield()->isEnabled() << endl;
			//		cout << "Shield activo? " << (*itS)->getShield()->isEnabled() << endl;
			//		cout << "Shiel Life (energia) " << (*itS)->getShieldEnergy() << endl;
			//		cout << "Bombas activas? " << (*itS)->isBombEnabled() << endl;
			//		cout << "Invisible activo? " << (*itS)->isInvisibilityEnabled() << endl;
			//		cout << "Laser activo? " << (*itS)->isLaserActivated() << endl;		
			//		cout << "Esta muerta? " << (*itS)->isDeadEnabled() << endl;
			//		cout << "Base Life (energia) " << (*itS)->getBase()->getLife() << endl;
			//	}
			//}
			// Actualizacio de bullets
			// Si un bullet de la lista del cliente no esta en la del servidor, entonces desaparecio y se tiene q borrar
			// Si un bullet de la lista del cliente esta en la del servidor, entonces se actualiza
			map<int, Bullet*>::iterator itClientB;
			for (itClientB=bullets.begin(); itClientB!=bullets.end(); )
			{
				if (bulletsData.find(itClientB->first) == bulletsData.end())
				{
					clientView->removeEntity(itClientB->second);
					//lo agruego a la lista de pendientes para borrar que se limpia en la proxima actualizacion
					pendingToDelete.push_back(itClientB->second);
					itClientB = bullets.erase(itClientB);
				}
				else
				{
					// actualizacion de la posicion y tipo
					BulletData& b = bulletsData[itClientB->first];
					itClientB->second->setPosition(b.x, b.y);
					itClientB->second->setRotation(b.rotation);
					itClientB->second->setType(static_cast<Bullet::BulletType>(b.type));
					itClientB++;
				}		
			}

			// Actualizacio de bullets
			// Si un bullet de la lista del servidor no esta en la del cliente, entonces es nueva y se agrega
			map<int, BulletData>::iterator itServerB;
			for (itServerB=bulletsData.begin(); itServerB!=bulletsData.end(); itServerB++)
			{
				if (bullets.find(itServerB->first) == bullets.end())
				{
					sound->freeChannelOne();
					BulletData& b = itServerB->second;
					Bullet::BulletType btype = static_cast<Bullet::BulletType>(b.type);
					Bullet* bullet = EntityFactory::instance().createBullet(b.id, btype, Point(b.x, b.y), 0, 0);
					bullet->setRotation(b.rotation);
					bullets.insert(pair<int, Bullet*>(b.id, bullet));
					clientView->addEntity(bullet);
					//agregar bala
					/*
					if (bullet->getType() == Bullet::BOMBEXPLOTED)
					{
						sound->playEffect("CHOQUE");
					}
					else
					{
						sound->playEffect("BALA");
					}
					*/
				}
			}

			// Actualizacion de bonus
			// Si un bonus de la lista del cliente no esta en la del servidor, entonces desaparecio y se tiene q borrar
			map<int, Bonus*>::iterator itClientX;
			for (itClientX=bonuses.begin(); itClientX!=bonuses.end(); )
			{
				if (bonusData.find(itClientX->first) == bonusData.end())
				{
					clientView->removeEntity(itClientX->second);
					//lo agrego a la lista de pendientes para borrar que se limpia en la proxima actualizacion
					pendingToDelete.push_back(itClientX->second);
					itClientX = bonuses.erase(itClientX);
				}
				else
				{
					//actualiza la posicion
					EntityData &x = bonusData[itClientX->first];
					itClientX->second->setPosition(x.x, x.y);					
					itClientX++;
				}		
			}

			// Si un bonus de la lista del servidor no esta en la del cliente, entonces es nuevo y se agrega
			map<int, EntityData>::iterator itServerX;
			for (itServerX=bonusData.begin(); itServerX!=bonusData.end(); itServerX++)
			{
				if (bonuses.find(itServerX->first) == bonuses.end())
				{
					EntityData &x = itServerX->second;				
					Bonus* bonus = EntityFactory::instance().createBonus(x.id, Point(x.x, x.y), Bonus::UNDEFINED, 0);
					if (x.type == Bonus::LASER)
						bonus->setImagePath(string(DEFAULT_BONUS_LASER));
					else if (x.type == Bonus::BOMB)
						bonus->setImagePath(string(DEFAULT_BONUS_BOMBA));
					else if (x.type == Bonus::SHIELD)
						bonus->setImagePath(string(DEFAULT_BONUS_ESCUDO));
					else if (x.type == Bonus::SUPER_SHIELD)
						bonus->setImagePath(string(DEFAULT_BONUS_SUPERESCUDO));
					else if (x.type == Bonus::INVISIBILITY)
						bonus->setImagePath(string(DEFAULT_BONUS_INVISIBLE));
					else if (x.type == Bonus::MISSILE)
						bonus->setImagePath(string(DEFAULT_BONUS_MISIL));
					else
						bonus->setImagePath(string(DEFAULT_BONUS));
					
					bonuses.insert(pair<int, Bonus*>(x.id, bonus));
					clientView->addEntity(bonus);
				}
			}
		}
	}
	catch (...)
	{
		gameFinished();
	}
}

void Bootstrap::updateServerStatus(string status)
{
	if (window->isVisible())
	{
		window->setStatus(status);
	}
}

void Bootstrap::updateClientStatus(string status)
{
	if (window->isVisible())
	{
		window->setStatus(status);
	}
}

void Bootstrap::gameFinished()
{
	// vuelve a mostrar menu
	if (clientView.get())
	{
		clientView->hide();
	}
	
	cleanPendingEntities();

	map<int, Asteroid*>::iterator itA;
	for (itA=asteroidsmap.begin(); itA!=asteroidsmap.end(); itA++)
	{
		if (itA->second)
			delete itA->second;
	}

	asteroidsmap.clear();
	bullets.clear();
	ships.clear();
	bonuses.clear();
	bases.clear();

	showGameView = false;
	sound->~Mixer();

	mainSurface->resetVideoMode(430, 550, 32);
	window->setStatus(" ");
	showMenu();
}

void Bootstrap::quitGame()
{
	cout << "Quitting game..." << endl;
	showGameView = false;
	if (clientView.get() != NULL)
	{		
		clientView->hide();
	}
}

void Bootstrap::quit()
{
	cout << "Closing program..." << endl;
	window->close();
}

int main(int argc, char *argv[])
{
	Logger::instance().log("Iniciando Asteroids");
	Bootstrap boot;
	boot.start();
	return 0;
}

void Bootstrap::setOwnShipId(int shipId)
{
	this->ownShipId = shipId;
}

void Bootstrap::setOtherPlayerName(string otherPlayerName)
{
	this->otherPlayerName = otherPlayerName;
}

string Bootstrap::getOtherPlayerName(void)
{
	return this->otherPlayerName;
}